from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
import bcrypt
import secrets
import base64
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import requests

# JWT 配置
SECRET_KEY = "aaAA1234"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# 微信信息配置,注意个人帐号不能获取phone信息。
WECHAT_APPID = "wxf86c906426805dce"
WECHAT_SECRET = "23bc4c22bd385eb5c43d864521480e3f"

# 密码哈希
def get_password_hash(password: str) -> str:
    # 编码为字节
    pwd_bytes = password.encode('utf-8')
    # 生成盐并哈希
    hashed = bcrypt.hashpw(pwd_bytes, bcrypt.gensalt())
    # 返回字符串形式
    return hashed.decode('utf-8')

# 验证密码
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))

# 创建访问令牌
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

# 生成验证码
def generate_captcha() -> tuple[str, str]:
    # 生成简单的数字验证码
    import random
    code = ''.join([str(random.randint(0, 9)) for _ in range(4)])
    # 生成一个临时密钥用于存储验证码
    captcha_key = secrets.token_urlsafe(16)
    return captcha_key, code
# 根据jscode获取session信息
def code2session(js_code: str) -> dict:
    """
    Call WeChat code2session to get session_key and openid.
    Returns dict with keys like 'session_key' and 'openid', or raises on error.
    """
    url = "https://api.weixin.qq.com/sns/jscode2session"
    params = {
        "appid": WECHAT_APPID,
        "secret": WECHAT_SECRET,
        "js_code": js_code,
        "grant_type": "authorization_code"
    }
    r = requests.get(url, params=params, timeout=5)
    r.raise_for_status()
    data = r.json()
    if "errcode" in data and data.get("errcode", 0) != 0:
        raise RuntimeError(f"code2session error: {data}")
    # data contains 'openid', 'session_key', maybe 'unionid'
    return data['session_key']

# 解码微信电话
def decrypt_wechat_phone(encrypted_data, iv, session_key):
    """
    Decrypt data using AES-128-CBC with PKCS#7 padding.
    
    :param encrypted_data: Base64 encoded encrypted data (str)
    :param iv: Base64 encoded IV (str)
    :param session_key: Base64 encoded session key (str)
    :return: Decrypted JSON data (dict) or None if error
    """
    # Decode inputs: support standard and urlsafe base64
    def _b64decode_try(s: str) -> bytes:
        try:
            return base64.b64decode(s)
        except Exception:
            try:
                return base64.urlsafe_b64decode(s)
            except Exception as e:
                raise RuntimeError(f"invalid base64 input: {e}")

    encrypted_data_bytes = _b64decode_try(encrypted_data)
    iv_bytes = _b64decode_try(iv)
    key_bytes = _b64decode_try(session_key)

    # Validate lengths
    if len(iv_bytes) != 16:
        raise RuntimeError(f"Invalid IV length: {len(iv_bytes)} bytes (expected 16)")
    if len(key_bytes) not in (16, 24, 32):
        raise RuntimeError(f"Invalid AES key length: {len(key_bytes)} bytes (expected 16/24/32)")

    # Create cipher object
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)

    # Decrypt and unpad
    decrypted_bytes = cipher.decrypt(encrypted_data_bytes)
    try:
        unpadded_bytes = unpad(decrypted_bytes, AES.block_size, style='pkcs7')
    except Exception as e:
        raise RuntimeError(f"PKCS7 unpad failed: {e}")

    # Decode to UTF-8 and parse JSON
    try:
        decrypt_result = json.loads(unpadded_bytes.decode('utf-8'))
        decrypt_result = decrypt_result['phoneNumber']

    except Exception as e:
        raise RuntimeError(f"JSON decode failed: {e}")

    return decrypt_result